#include "Material.h"
#include "Basic/Stream.h"
#include "Basic/FileSystem.h"
#include "Windows/GxDebug.h"

namespace FinalRendering
{
	namespace Engine
	{
		using namespace GxMetaParser;

		RefPtr<MetaLexer> MaterialFile::lexer;

		MaterialFile::MaterialFile()
		{
		}

		MaterialFile::MaterialFile(const String & fileName)
		{
			Load(fileName);
		}

		void MaterialFile::Load(const String & fileName)
		{
			FileName = fileName;
			String str = ReadTextFile(fileName);
			LexStream stream;
			lexer->Parse(str, stream);
			UnSerialize(stream.Begin());
		}

		void MaterialFile::Save(const String & fileName)
		{
			StringBuilder sb(40960);
			Serialize(sb);
			WriteTextFile(fileName, sb.GetStrPtr());
		}

		MaterialFile::Uniform * MaterialFile::FindUniform(const String & name)
		{
			for (int i=0; i<Uniforms.Count(); i++)
			{
				if (Uniforms[i]->Name == name)
				{
					return Uniforms[i].operator->();
				}
			}
			return 0;
		}

		void MaterialFile::Serialize(StringBuilder & sb)
		{
			int level = 0;
			sb.WriteString(ShaderName);
			BlockBegin(sb, level);
			{
				for (int i=0; i<Uniforms.Count(); i++)
				{
					switch (Uniforms[i]->Type)
					{
					case Uniform::Float:
						WriteFloat(sb, level, Uniforms[i]->Name, Uniforms[i]->FloatVal);
						break;
					case Uniform::Int:
						WriteInt(sb, level, Uniforms[i]->Name, Uniforms[i]->IntVal);
						break;
					case Uniform::Vec3:
						{
							NewLine(sb, level);
							PrintTab(sb, level);
							sb.WriteString(Uniforms[i]->Name);
							sb.WriteString(L" [");
							WriteVector(sb, Uniforms[i]->VecVal);
							sb.WriteChar(L']');
						}
						break;
					case Uniform::Texture:
						WriteString(sb, level, Uniforms[i]->Name, Uniforms[i]->Value);
					}
				}
			}
			BlockEnd(sb,level);
		}

		bool MaterialFile::UnSerialize(TokenNode * token)
		{
			try
			{
				ShaderName = ReadToken(token, Lex::Identifier)->Data.Str;
				ReadToken(token, Lex::LBrace);
				while (token && token->Data.TypeID != (int)Lex::RBrace)
				{
					String name = ReadToken(token, Lex::Identifier)->Data.Str;
					RefPtr<Uniform> u = new Uniform();
					u->Name = name;
					if (token->Data.TypeID == (int)Lex::Int)
					{
						u->Type = Uniform::Int;
						ReadInt(token, &u->IntVal);
					}
					else if (token->Data.TypeID == (int)Lex::Float)
					{
						u->Type = Uniform::Float;
						ReadFloat(token, &u->FloatVal);
					}
					else if (token->Data.TypeID == (int)Lex::LBracket)
					{
						u->Type = Uniform::Vec3;
						ReadToken(token, Lex::LBracket);
						ReadVector(token, &u->VecVal);
						ReadToken(token, Lex::RBracket);
					}
					else if (token->Data.TypeID == (int)Lex::String)
					{
						u->Type = Uniform::Texture;
						ReadString(token, &u->Value);
					}
					else
						throw 0;
					Uniforms.Add(u);
				}
				ReadToken(token, Lex::RBrace);
			}
			catch(...)
			{
				return false;
			}
			return true;
		}

		void MaterialFile::Init()
		{
			if (!lexer)
			{
				String profile =
					L"Int = {-?\\d+}\n"\
					L"Float = {-?\\d+(.\\d*)?}\n"\
					L"String = {\"([^\\\\\"]|\\\\\\.)*\"}\n"\
					L"Comma = {,}\n"\
					L"LBrace = {{}\n"\
					L"RBrace = {}}\n"\
					L"LBracket = {\\[}\n"\
					L"RBracket = {\\]}\n"\
					L"Identifier = {[a-zA-Z_]\\w*}\n"\
					L"#WhiteSpace = {\\s+}\n"\
					L"#SingleLineComment = {//[^\\n]*\\n}\n"\
					L"#MultiLineComment = {/\\*([^*]|\\*[^/])*\\*/}\n";
				lexer = new MetaLexer(profile);
			}
		}

		void MaterialFile::Finalize()
		{
			lexer = 0;
		}

		Program * ProgramPool::LoadProgram(const String & shaderName)
		{
			String sname = shaderName;
			sname.Uppercase();
			int fid = progNames.Find(sname);
			if (fid != -1)
			{
				return programs[fid].operator->();
			}
			else
			{
				RefPtr<Program> p = new Program(String(L"Shaders/")+shaderName+L".gxprog");
				programs.Add(p);
				progNames.Add(sname);
				return p.operator ->();
			}
		}

		Material::Material(const MaterialFile & matFile, ProgramPool * prog)
			: DiffuseRate(1.0f,1.0f,1.0f)
		{
			shaderName = matFile.ShaderName;
			program = prog->LoadProgram(matFile.ShaderName);
			for (int i=0; i<matFile.Uniforms.Count(); i++)
			{
				Uniform u;
				u.Location = program->GetUniformLocation(matFile.Uniforms[i]->Name);
				switch (matFile.Uniforms[i]->Type)
				{
				case MaterialFile::Uniform::Int:
					u.Type = Uniform::Int;
					u.IntVal = matFile.Uniforms[i]->IntVal;
					break;
				case MaterialFile::Uniform::Texture:
					{
						u.Type = Uniform::Int;
						Texture * tex = new Texture();
						tex->LoadFromFile(ExtractFilePath(matFile.FileName)+
							matFile.Uniforms[i]->Value);
						textures.Add(tex);
						u.IntVal = textures.Count()-1;
						if (matFile.Uniforms[i]->Name.Lowercase() == L"diffuse_map")
						{
							DiffuseRate = tex->GetAverageColor();
						}
					}
					break;
				case MaterialFile::Uniform::Float:
					u.Type = Uniform::Float;
					u.FloatVal = matFile.Uniforms[i]->FloatVal;
					break;
				case MaterialFile::Uniform::Vec3:
					{
						u.Type = Uniform::Vec3;
						vec3 & v = matFile.Uniforms[i]->VecVal;
						u.VecX = v.x;
						u.VecY = v.y;
						u.VecZ = v.z;
					}
					break;
				}
				Uniforms.Add(u);
			}
		}

		Texture * Material::GetDiffuseTexture()
		{
			if (textures.Count())
				return textures[0].operator ->();
			return 0;
		}

		vec3 Material::GetDiffuseRate()
		{
			return DiffuseRate;
		}

		String Material::GetShaderName()
		{
			return shaderName;
		}

		int Material::GetTextureCount()
		{
			return textures.Count();
		}

		Program * Material::GetProgram()
		{
			return program;
		}

		void Material::Bind(Object * targetObj, bool switchProgram)
		{
			if (switchProgram)
				program->Bind();
			for (int i=0; i<textures.Count(); i++)
			{
				MultiTexture::ActiveTexture(GL_TEXTURE0+i);
				glEnable(GL_TEXTURE_2D);
				textures[i]->Bind();
			}
			for (int i=0; i<Uniforms.Count(); i++)
			{
				switch (Uniforms[i].Type)
				{
				case Uniform::Int:
					program->SetUniform(Uniforms[i].Location, Uniforms[i].IntVal);
					break;
				case Uniform::Float:
					program->SetUniform(Uniforms[i].Location, Uniforms[i].FloatVal);
					break;
				case Uniform::Vec3:
					program->SetUniform(Uniforms[i].Location, vec3(Uniforms[i].VecX,
						Uniforms[i].VecY, Uniforms[i].VecZ));
					break;
				}
			}
			for (int i=0; i<program->GetAutoUniformCount(); i++)
			{
				int loc = Shader::GetUniformLocationARB(program->GetHandle(), program->GetAutoUniform(i).ToMBString());
				if (loc != -1)
					OnSetClientUniform.Invoke(this, targetObj, program->GetAutoUniform(i), loc);
			}
			/*if (!program->Validate())
			{
				GxWinSystem::Debug::WriteLine("Failed to validate program");
			}*/
		}
	}
}